package org.spring.aop.framework;

import lombok.AllArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring.aop.AdvisedSupport;
import org.spring.aop.MethodMatcher;
import org.tool.reflect.ClassLoaderUtil;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Spring AOP 框架的基于 JDK 的 {@link AopProxy} 实现，基于 JDK {@link Proxy 动态代理}。
 * <p>
 * 创建一个动态代理，实现 AopProxy 公开的接口。动态代理<i>不能<i>用于代理类中定义的方法，而不是接口。
 * <p>
 * 这种类型的对象应该通过代理工厂获得，由 {@link AdvisedSupport} 类配置。这个类是 Spring 的 AOP 框架内部的，不需要由客户端代码直接使用。
 * <p>
 * 如果底层（目标）类是线程安全的，则使用此类创建的代理将是线程安全的。
 * <p>
 * 只要所有顾问（包括建议和切入点）和 TargetSource 都是可序列化的，代理就是可序列化的。
 *
 * @author 李坤
 * @date 2022/4/18 10:15
 */
@AllArgsConstructor
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

    private static final Logger logger = LoggerFactory.getLogger(JdkDynamicAopProxy.class);

    /**
     * AOP 代理配置管理器的基类
     */
    private final AdvisedSupport advised;

    @Override
    public Object getProxy() {
        return this.getProxy(ClassLoaderUtil.getDefaultClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        // 获取被代理对象的 Class 类
        Class<?> targetClass = advised.getTargetSource().getTargetClass();
        return Proxy.newProxyInstance(classLoader, targetClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.debug("开始创建 JDK 动态代理对象...");

        // 获取被代理对象
        Object target = advised.getTargetSource().getTarget();

        // 判断是否匹配给定的切点 TODO 此处有Bug：无法匹配到被代理类上的方法
        if (this.matchTargetMethod(target, method)) {
            // 获取代理对象的方法拦截器（代理/增强方法）
            MethodInterceptor methodInterceptor = advised.getMethodInterceptor();
            // 执行代理对象的方法拦截器（代理/增强方法）
            return methodInterceptor.invoke(new ReflectiveMethodInvocation(target, method, args, proxy));
        }
        // 执行被代理对象的方法
        return method.invoke(target, args);
    }

    /*--------------------------------------------------private methods-----------------------------------------------*/

    /**
     * 判断是否匹配给定的切点
     *
     * @param target 被代理对象
     * @param method 被代理对象的方法
     *
     * @return 是否匹配给定的切点
     */
    private boolean matchTargetMethod(Object target, Method method) {
        logger.debug("开始判断方法是否匹配...");

        boolean matches = false;
        Class<?> targetClass = target.getClass();
        MethodMatcher methodMatcher = advised.getMethodMatcher();

        logger.debug("开始匹配：类 - {}，方法 - {}", targetClass.getName(), method);

        if (!(matches = methodMatcher.matches(method, targetClass))) {
            for (Class<?> clazz : targetClass.getInterfaces()) {
                logger.debug("开始匹配：类 - {}，方法 - {}", clazz, method);

                if (methodMatcher.matches(method, clazz)) {
                    matches = true;
                    break;
                }
            }
        }

        logger.debug("方法是否匹配：{}", matches);
        return matches;
    }

}
